import {
  Breakpoint,
  Handles,
  InitializedEvent,
  Logger,
  logger,
  LoggingDebugSession,
  Scope,
  Source,
  StackFrame,
  StoppedEvent,
  TerminatedEvent,
  Thread,
} from '@vscode/debugadapter';
import { DebugProtocol } from '@vscode/debugprotocol';
import { Subject } from 'await-notify';
import { CloudDebugRuntime } from './cloud-debug-runtime';
import { IAttachRequestArguments, ILaunchRequestArguments } from './interface';
import { RuntimeVariable } from './runtime-variable';
import { DebugRuntimeEvent } from './constants';

export class CloudDebugSession extends LoggingDebugSession {
  private static threadID = 1;

  private rt: CloudDebugRuntime;

  private _variableHandles = new Handles<
    'locals' | 'globals' | RuntimeVariable
  >();

  private _configurationDone: Subject;

  constructor() {
    super();

    this.rt = new CloudDebugRuntime();

    this.rt.on(DebugRuntimeEvent.ENTRY, () => {
      this.sendEvent(new StoppedEvent('entry', CloudDebugSession.threadID));
    });
    this.rt.on(DebugRuntimeEvent.STEP, () => {
      this.sendEvent(new StoppedEvent('step', CloudDebugSession.threadID));
    });
    this.rt.on(DebugRuntimeEvent.BREAKPOINT, () => {
      this.sendEvent(
        new StoppedEvent('breakpoint', CloudDebugSession.threadID),
      );
    });
    this.rt.on('stopOnException', exception => {
      if (exception) {
        this.sendEvent(
          new StoppedEvent(
            `exception(${exception})`,
            CloudDebugSession.threadID,
          ),
        );
      } else {
        this.sendEvent(
          new StoppedEvent('exception', CloudDebugSession.threadID),
        );
      }
    });
    this.rt.on('end', () => {
      this.sendEvent(new TerminatedEvent());
    });
  }

  /**
   * 前端调用的第一个请求，用来确认提供了哪些调试功能
   *
   * @author chitanda
   * @date 2022-11-28 11:11:20
   * @protected
   * @param {DebugProtocol.InitializeResponse} response
   * @param {DebugProtocol.InitializeRequestArguments} args
   */
  protected initializeRequest(
    response: DebugProtocol.InitializeResponse,
    _args: DebugProtocol.InitializeRequestArguments,
  ): void {
    // 生成并返回此调试适配器的功能
    response.body = response.body || {};

    // 适配器实现 configurationDone请求
    response.body.supportsConfigurationDoneRequest = true;

    // 在悬浮在源码上时，触发求值请求
    response.body.supportsEvaluateForHovers = true;

    // 支持后退按钮
    response.body.supportsStepBack = true;

    // 支持数据断点
    response.body.supportsDataBreakpoints = true;

    // 支持 REPL 完成
    response.body.supportsCompletionsRequest = true;
    response.body.completionTriggerCharacters = ['.', '['];

    // 取消请求
    response.body.supportsCancelRequest = true;

    // 断点位置请求
    response.body.supportsBreakpointLocationsRequest = true;

    // make VS Code provide "Step in Target" functionality
    response.body.supportsStepInTargetsRequest = true;

    // 适配器定义了两个过滤器，一个支持条件
    response.body.supportsExceptionFilterOptions = true;
    response.body.exceptionBreakpointFilters = [
      {
        filter: 'namedException',
        label: 'Named Exception',
        description: `Break on named exceptions. Enter the exception's name as the Condition.`,
        default: false,
        supportsCondition: true,
        conditionDescription: `Enter the exception's name`,
      },
      {
        filter: 'otherExceptions',
        label: 'Other Exceptions',
        description: 'This is a other exception',
        default: true,
        supportsCondition: false,
      },
    ];

    // 使 VS Code 发送异常信息请求
    response.body.supportsExceptionInfoRequest = true;

    // 使 VS Code 发送设置变量请求
    response.body.supportsSetVariable = true;

    // 使 VS Code 发送设置异常请求
    response.body.supportsSetExpression = true;

    // 使 VS Code 二进制相关
    response.body.supportsDisassembleRequest = false;
    response.body.supportsSteppingGranularity = false;
    response.body.supportsInstructionBreakpoints = false;

    // 使 VS Code 可读取和写入可变内存
    response.body.supportsReadMemoryRequest = false;
    response.body.supportsWriteMemoryRequest = false;

    response.body.supportSuspendDebuggee = false;
    response.body.supportTerminateDebuggee = true;
    response.body.supportsFunctionBreakpoints = false;
    response.body.supportsDelayedStackTraceLoading = true;

    this.sendResponse(response);

    // since this debug adapter can accept configuration requests like 'setBreakpoint' at any time,
    // we request them early by sending an 'initializeRequest' to the frontend.
    // The frontend will end the configuration sequence by calling 'configurationDone' request.
    this.sendEvent(new InitializedEvent());
  }

  /**
   * 所有配置序列结束
   *
   * @description 表示所有断点等均已发送至调试器
   * @author chitanda
   * @date 2022-11-30 11:11:11
   * @protected
   * @param {DebugProtocol.ConfigurationDoneResponse} response
   * @param {DebugProtocol.ConfigurationDoneArguments} args
   */
  protected configurationDoneRequest(
    response: DebugProtocol.ConfigurationDoneResponse,
    args: DebugProtocol.ConfigurationDoneArguments,
  ): void {
    super.configurationDoneRequest(response, args);

    // 通知 launchRequest 配置已完成
    this._configurationDone.notify();
    this._configurationDone = null;
  }

  /**
   * 断开 Debug 连接
   *
   * @author chitanda
   * @date 2022-11-30 11:11:02
   * @protected
   * @param {DebugProtocol.DisconnectResponse} _response
   * @param {DebugProtocol.DisconnectArguments} args
   * @param {DebugProtocol.Request} [_request]
   */
  protected disconnectRequest(
    _response: DebugProtocol.DisconnectResponse,
    _args: DebugProtocol.DisconnectArguments,
    _request?: DebugProtocol.Request,
  ): void {
    this.rt.close();
  }

  protected async attachRequest(
    response: DebugProtocol.AttachResponse,
    args: IAttachRequestArguments,
  ) {
    return this.launchRequest(response, args);
  }

  /**
   * 开始
   *
   * @author chitanda
   * @date 2022-11-30 11:11:36
   * @protected
   * @param {DebugProtocol.LaunchResponse} response
   * @param {ILaunchRequestArguments} args
   */
  protected async launchRequest(
    response: DebugProtocol.LaunchResponse,
    args: ILaunchRequestArguments,
  ) {
    // make sure to 'Stop' the buffered logging if 'trace' is not set
    logger.setup(
      args.trace ? Logger.LogLevel.Verbose : Logger.LogLevel.Stop,
      false,
    );

    // 初始化配置完成事件
    this._configurationDone = new Subject();

    const success = await this.rt.start(args.system, args.topic);

    // 事件还在，则等待事件完成
    if (this._configurationDone) {
      // 等待1秒，直到配置完成（并且调用了configurationDoneRequest）
      await this._configurationDone.wait(1000);
    }

    if (!success) {
      this.sendErrorResponse(response, {
        id: 30000,
        format: `未能成功打开远程调试!`,
      });
    } else {
      this.sendResponse(response);
    }
  }

  /**
   * 给指定文件设置断点
   *
   * @author chitanda
   * @date 2022-11-28 11:11:36
   * @protected
   * @param {DebugProtocol.SetBreakpointsResponse} response
   * @param {DebugProtocol.SetBreakpointsArguments} args
   * @return {*}  {Promise<void>}
   */
  protected async setBreakPointsRequest(
    response: DebugProtocol.SetBreakpointsResponse,
    args: DebugProtocol.SetBreakpointsArguments,
  ): Promise<void> {
    const path = args.source.path as string;
    const clientLines = args.lines || [];
    // 清空当前文件所有断点
    await this.rt.clearBreakpoints(path);

    // set and verify breakpoint locations
    const all = clientLines.map(async l => {
      const { verified, line, id } = await this.rt.setBreakPoint(
        path,
        this.convertClientLineToDebugger(l),
      );
      const bp = new Breakpoint(
        verified,
        this.convertDebuggerLineToClient(line),
      ) as DebugProtocol.Breakpoint;
      bp.id = id;
      return bp;
    });

    const breakpoints = await Promise.all<DebugProtocol.Breakpoint>(all);

    // 返回实际断点位置
    response.body = {
      breakpoints,
    };
    this.sendResponse(response);
  }

  /**
   * 请求断点位置
   *
   * @author chitanda
   * @date 2022-11-30 15:11:07
   * @protected
   * @param {DebugProtocol.BreakpointLocationsResponse} response
   * @param {DebugProtocol.BreakpointLocationsArguments} args
   * @param {DebugProtocol.Request} [_request]
   */
  protected breakpointLocationsRequest(
    response: DebugProtocol.BreakpointLocationsResponse,
    args: DebugProtocol.BreakpointLocationsArguments,
    _request?: DebugProtocol.Request,
  ): void {
    if (args.source.path) {
      const bps = this.rt.getBreakpoints(
        args.source.path,
        this.convertClientLineToDebugger(args.line),
      );
      response.body = {
        breakpoints: bps.map(col => {
          return {
            line: args.line,
            column: this.convertDebuggerColumnToClient(col),
          };
        }),
      };
    } else {
      response.body = {
        breakpoints: [],
      };
    }
    this.sendResponse(response);
  }

  // protected async setExceptionBreakPointsRequest(
  //   response: DebugProtocol.SetExceptionBreakpointsResponse,
  //   args: DebugProtocol.SetExceptionBreakpointsArguments,
  // ): Promise<void> {
  //   console.log(`执行: setExceptionBreakPointsRequest`);
  //   let namedException: string | undefined;
  //   let otherExceptions = false;

  //   if (args.filterOptions) {
  //     for (const filterOption of args.filterOptions) {
  //       switch (filterOption.filterId) {
  //         case 'namedException':
  //           namedException = args.filterOptions[0].condition;
  //           break;
  //         case 'otherExceptions':
  //           otherExceptions = true;
  //           break;
  //       }
  //     }
  //   }

  //   if (args.filters) {
  //     if (args.filters.indexOf('otherExceptions') >= 0) {
  //       otherExceptions = true;
  //     }
  //   }

  //   this.rt.setExceptionsFilters(namedException, otherExceptions);

  //   this.sendResponse(response);
  // }

  // protected exceptionInfoRequest(
  //   response: DebugProtocol.ExceptionInfoResponse,
  //   _args: DebugProtocol.ExceptionInfoArguments,
  // ) {
  //   console.log(`执行: exceptionInfoRequest`);
  //   response.body = {
  //     exceptionId: 'Exception ID',
  //     description: 'This is a descriptive description of the exception.',
  //     breakMode: 'always',
  //     details: {
  //       message: 'Message contained in the exception.',
  //       typeName: 'Short type name of the exception object',
  //       stackTrace: 'stack frame 1\nstack frame 2',
  //     },
  //   };
  //   this.sendResponse(response);
  // }

  /**
   * 请求当前 debug 的线程
   *
   * @author chitanda
   * @date 2022-11-30 14:11:10
   * @protected
   * @param {DebugProtocol.ThreadsResponse} response
   */
  protected threadsRequest(response: DebugProtocol.ThreadsResponse): void {
    response.body = {
      threads: [new Thread(CloudDebugSession.threadID, '默认进程')],
    };
    this.sendResponse(response);
  }

  /**
   * 请求堆栈信息
   *
   * @description 远程调试暂不支持堆栈信息，返回默认堆栈信息
   * @author chitanda
   * @date 2022-11-30 14:11:32
   * @protected
   * @param {DebugProtocol.StackTraceResponse} response
   * @param {DebugProtocol.StackTraceArguments} args
   */
  protected stackTraceRequest(
    response: DebugProtocol.StackTraceResponse,
    args: DebugProtocol.StackTraceArguments,
  ): void {
    const startFrame =
      typeof args.startFrame === 'number' ? args.startFrame : 0;
    const maxLevels = typeof args.levels === 'number' ? args.levels : 1000;
    const endFrame = startFrame + maxLevels;

    const stk = this.rt.stack(startFrame, endFrame);

    response.body = {
      stackFrames: stk.frames.map((f, _ix) => {
        const sf: DebugProtocol.StackFrame = new StackFrame(
          f.index,
          f.name,
          this.createSource(f.file),
          this.convertDebuggerLineToClient(f.line),
        );
        if (typeof f.column === 'number') {
          sf.column = this.convertDebuggerColumnToClient(f.column);
        }
        return sf;
      }),
      totalFrames: stk.count,
    };
    this.sendResponse(response);
  }

  /**
   * 获取变量域
   *
   * @author chitanda
   * @date 2022-11-30 14:11:26
   * @protected
   * @param {DebugProtocol.ScopesResponse} response
   * @param {DebugProtocol.ScopesArguments} _args
   */
  protected scopesRequest(
    response: DebugProtocol.ScopesResponse,
    _args: DebugProtocol.ScopesArguments,
  ): void {
    response.body = {
      scopes: [
        new Scope('当前变量', this._variableHandles.create('locals'), false),
        new Scope('全局变量', this._variableHandles.create('globals'), false),
      ],
    };
    this.sendResponse(response);
  }

  /**
   * 请求变量内容
   *
   * @author chitanda
   * @date 2022-11-30 14:11:39
   * @protected
   * @param {DebugProtocol.VariablesResponse} response
   * @param {DebugProtocol.VariablesArguments} args
   * @param {DebugProtocol.Request} [request]
   * @return {*}  {Promise<void>}
   */
  protected async variablesRequest(
    response: DebugProtocol.VariablesResponse,
    args: DebugProtocol.VariablesArguments,
    _request?: DebugProtocol.Request,
  ): Promise<void> {
    let vs: RuntimeVariable[] = [];

    const v = this._variableHandles.get(args.variablesReference);
    if (v === 'locals') {
      vs = this.rt.getLocalVariables();
    } else if (v === 'globals') {
      vs = this.rt.getGlobalVariables();
    } else if (v && !v.isBasicDataType) {
      if (Array.isArray(v.value)) {
        vs = v.value;
      } else {
        const items = await this.rt.listFields(v.parameterPath);
        items.forEach(item => {
          item.parent = v;
        });
        v.value = items;
        vs = items;
      }
    }

    response.body = {
      variables: vs.map(item => this.convertFromRuntime(item)),
    };
    this.sendResponse(response);
  }

  /**
   * 修改断点变量值
   *
   * @author chitanda
   * @date 2022-11-30 14:11:59
   * @protected
   * @param {DebugProtocol.SetVariableResponse} response
   * @param {DebugProtocol.SetVariableArguments} args
   */
  // protected setVariableRequest(
  //   response: DebugProtocol.SetVariableResponse,
  //   args: DebugProtocol.SetVariableArguments,
  // ): void {
  //   const container = this._variableHandles.get(args.variablesReference);
  //   let rv: RuntimeVariable | undefined;
  //   if (container === 'locals') {
  //     rv = this.rt.getLocalVariable(args.name);
  //   } else if (
  //     container instanceof RuntimeVariable &&
  //     container.value instanceof Array
  //   ) {
  //     rv = container.value.find(v => v.name === args.name);
  //   }

  //   if (rv) {
  //     // rv.value = this.convertToRuntime(args.value);
  //     // response.body = this.convertFromRuntime(rv);
  //     // if (rv.memory && rv.reference) {
  //     //   this.sendEvent(
  //     //     new MemoryEvent(String(rv.reference), 0, rv.memory.length),
  //     //   );
  //     // }
  //   }

  //   this.sendResponse(response);
  // }

  /**
   * 执行跳过请求
   *
   * @author chitanda
   * @date 2022-11-30 14:11:06
   * @protected
   * @param {DebugProtocol.ContinueResponse} response
   * @param {DebugProtocol.ContinueArguments} _args
   */
  protected async continueRequest(
    response: DebugProtocol.ContinueResponse,
    _args: DebugProtocol.ContinueArguments,
  ): Promise<void> {
    await this.rt.continue(false);
    this.sendResponse(response);
  }

  /**
   * 单步跳过
   *
   * @author chitanda
   * @date 2022-11-30 14:11:03
   * @protected
   * @param {DebugProtocol.NextResponse} response
   * @param {DebugProtocol.NextArguments} args
   */
  protected nextRequest(
    response: DebugProtocol.NextResponse,
    args: DebugProtocol.NextArguments,
  ): void {
    this.rt.step(args.granularity === 'instruction', false);
    this.sendResponse(response);
  }

  /**
   * 后退
   *
   * @author chitanda
   * @date 2022-11-30 15:11:22
   * @protected
   * @param {DebugProtocol.StepBackResponse} response
   * @param {DebugProtocol.StepBackArguments} args
   */
  protected stepBackRequest(
    response: DebugProtocol.StepBackResponse,
    args: DebugProtocol.StepBackArguments,
  ): void {
    this.rt.step(args.granularity === 'instruction', true);
    this.sendResponse(response);
  }

  /**
   * 单步执行目标
   *
   * @author chitanda
   * @date 2022-11-30 19:11:07
   * @protected
   * @param {DebugProtocol.StepInTargetsResponse} response
   * @param {DebugProtocol.StepInTargetsArguments} args
   */
  protected stepInTargetsRequest(
    response: DebugProtocol.StepInTargetsResponse,
    args: DebugProtocol.StepInTargetsArguments,
  ) {
    console.log(`执行: stepInTargetsRequest`);
    const targets = this.rt.getStepInTargets(args.frameId);
    response.body = {
      targets: targets.map(t => {
        return { id: t.id, label: t.label };
      }),
    };
    this.sendResponse(response);
  }

  /**
   * 单步调试
   *
   * @author chitanda
   * @date 2022-11-30 15:11:32
   * @protected
   * @param {DebugProtocol.StepInResponse} response
   * @param {DebugProtocol.StepInArguments} args
   */
  protected stepInRequest(
    response: DebugProtocol.StepInResponse,
    args: DebugProtocol.StepInArguments,
  ): void {
    this.rt.stepIn(args.targetId);
    this.sendResponse(response);
  }

  /**
   * 单步跳出
   *
   * @author chitanda
   * @date 2022-11-30 15:11:03
   * @protected
   * @param {DebugProtocol.StepOutResponse} response
   * @param {DebugProtocol.StepOutArguments} _args
   */
  protected stepOutRequest(
    response: DebugProtocol.StepOutResponse,
    _args: DebugProtocol.StepOutArguments,
  ): void {
    this.rt.stepOut();
    this.sendResponse(response);
  }

  protected async evaluateRequest(
    response: DebugProtocol.EvaluateResponse,
    args: DebugProtocol.EvaluateArguments,
  ): Promise<void> {
    const name = args.expression;

    let rv: RuntimeVariable | undefined;

    rv = this.rt.getLocalVariable(name);

    if (!rv) {
      rv = this.rt.getGlobalVariable(name);
    }

    if (!rv) {
      rv = await this.rt.evaluate(name);
    }

    if (rv) {
      const v = this.convertFromRuntime(rv);
      response.body = {
        result: v.value,
        type: v.type,
        variablesReference: v.variablesReference,
        presentationHint: v.presentationHint,
      };
    }

    this.sendResponse(response);
  }

  // protected setExpressionRequest(
  //   response: DebugProtocol.SetExpressionResponse,
  //   args: DebugProtocol.SetExpressionArguments,
  // ): void {
  //   console.log(`执行: setExpressionRequest`);
  //   if (args.expression.startsWith('$')) {
  //     const rv = this.rt.getLocalVariable(args.expression.substr(1));
  //     if (rv) {
  //       // rv.value = this.convertToRuntime(args.value);
  //       // response.body = this.convertFromRuntime(rv);
  //       this.sendResponse(response);
  //     } else {
  //       this.sendErrorResponse(response, {
  //         id: 1002,
  //         format: `variable '{lexpr}' not found`,
  //         variables: { lexpr: args.expression },
  //         showUser: true,
  //       });
  //     }
  //   } else {
  //     this.sendErrorResponse(response, {
  //       id: 1003,
  //       format: `'{lexpr}' not an assignable expression`,
  //       variables: { lexpr: args.expression },
  //       showUser: true,
  //     });
  //   }
  // }

  /**
   * 获取断点指定变量信息
   *
   * @author chitanda
   * @date 2022-11-30 18:11:09
   * @protected
   * @param {DebugProtocol.DataBreakpointInfoResponse} response
   * @param {DebugProtocol.DataBreakpointInfoArguments} args
   */
  protected dataBreakpointInfoRequest(
    response: DebugProtocol.DataBreakpointInfoResponse,
    args: DebugProtocol.DataBreakpointInfoArguments,
  ): void {
    response.body = {
      dataId: null,
      description: 'cannot break on data access',
      accessTypes: undefined,
      canPersist: false,
    };

    if (args.variablesReference && args.name) {
      const v = this._variableHandles.get(args.variablesReference);
      if (v === 'globals') {
        response.body.dataId = args.name;
        response.body.description = args.name;
        response.body.accessTypes = ['write'];
        response.body.canPersist = true;
      } else {
        response.body.dataId = args.name;
        response.body.description = args.name;
        response.body.accessTypes = ['read', 'write', 'readWrite'];
        response.body.canPersist = true;
      }
    }

    this.sendResponse(response);
  }

  // protected completionsRequest(
  //   response: DebugProtocol.CompletionsResponse,
  //   _args: DebugProtocol.CompletionsArguments,
  // ): void {
  //   console.log(`执行: completionsRequest`);
  //   response.body = {
  //     targets: [
  //       {
  //         label: 'item 10',
  //         sortText: '10',
  //       },
  //       {
  //         label: 'item 1',
  //         sortText: '01',
  //         detail: 'detail 1',
  //       },
  //       {
  //         label: 'item 2',
  //         sortText: '02',
  //         detail: 'detail 2',
  //       },
  //       {
  //         label: 'array[]',
  //         selectionStart: 6,
  //         sortText: '03',
  //       },
  //       {
  //         label: 'func(arg)',
  //         selectionStart: 5,
  //         selectionLength: 3,
  //         sortText: '04',
  //       },
  //     ],
  //   };
  //   this.sendResponse(response);
  // }

  private createSource(filePath: string): Source {
    return new Source(
      // basename(filePath),
      filePath,
      this.convertDebuggerPathToClient(filePath),
      undefined,
      undefined,
      'adapter-data',
    );
  }

  /**
   * 计算参数项
   *
   * @author chitanda
   * @date 2022-12-14 17:12:45
   * @private
   * @param {RuntimeVariable} rv
   * @return {*}  {DebugProtocol.Variable}
   */
  private convertFromRuntime(rv: RuntimeVariable): DebugProtocol.Variable {
    let reference = 0;
    if (rv.reference !== -1 && this._variableHandles.get(rv.reference)) {
      reference = rv.reference;
    } else {
      reference = this._variableHandles.create(rv);
      rv.reference = reference;
    }

    const dapVariable: DebugProtocol.Variable = {
      name: rv.name,
      value: rv.type,
      type: rv.type,
      variablesReference: rv.isBasicDataType ? 0 : reference,
      evaluateName: rv.parameterPath,
    };

    if (rv.isBasicDataType) {
      dapVariable.value = rv.value ? rv.value.toString() : 'null';
    }

    return dapVariable;
  }
}
